/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the AndroidAnnotations project
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.holder;

import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.IJExpression;
import com.helger.jcodemodel.JBlock;
import com.helger.jcodemodel.JDefinedClass;
import com.helger.jcodemodel.JFieldVar;
import com.helger.jcodemodel.JInvocation;
import com.helger.jcodemodel.JMethod;
import com.helger.jcodemodel.JVar;

import org.ohosannotations.OhosAnnotationsEnvironment;
import org.ohosannotations.annotations.EBean;
import org.ohosannotations.api.bean.BeanHolder;

import java.util.List;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;

import static com.helger.jcodemodel.JExpr._new;
import static com.helger.jcodemodel.JExpr._null;
import static com.helger.jcodemodel.JExpr.cast;
import static com.helger.jcodemodel.JMod.FINAL;
import static com.helger.jcodemodel.JMod.PRIVATE;
import static com.helger.jcodemodel.JMod.PUBLIC;
import static com.helger.jcodemodel.JMod.STATIC;
import static com.helger.jcodemodel.JMod.SYNCHRONIZED;
import static org.ohosannotations.helper.ModelConstants.generationSuffix;

/**
 * ebean持有人
 *
 * @since 2021-07-21
 */
public class EBeanHolder extends EComponentWithViewSupportHolder {
    /**
     * 实例方法名字
     */
    public static final String GET_INSTANCE_METHOD_NAME = "getInstance" + generationSuffix();
    private static final String STRING_BEAN = "bean";
    private static final String STRING_CONTEXT = "context";
    private static final String STRING_ROOT_FRACTION = "rootFraction";

    private JFieldVar rootFragmentField;
    private JFieldVar contextField;
    private JFieldVar tagField;

    private JMethod constructor;
    private JMethod overloadedConstructor;

    /**
     * ebean持有人
     * EBeanHolder
     *
     * @param environment environment
     * @param annotatedElement annotatedElement
     * @throws Exception 异常
     */
    public EBeanHolder(OhosAnnotationsEnvironment environment, TypeElement annotatedElement) throws Exception {
        super(environment, annotatedElement);
        setConstructors();
    }

    /**
     * 构造函数设置
     */
    private void setConstructors() {
        constructor = generatedClass.constructor(PRIVATE);
        JVar constructorContextParam = constructor.param(getClasses().CONTEXT, STRING_CONTEXT);
        JBlock constructorBody = constructor.body();
        List<ExecutableElement> constructors = ElementFilter.constructorsIn(annotatedElement.getEnclosedElements());
        ExecutableElement superConstructor = constructors.get(0);
        if (superConstructor.getParameters().size() == 1) {
            constructorBody.invoke("super").arg(constructorContextParam);
        }
        constructorBody.assign(getContextField(), constructorContextParam);

        overloadedConstructor = generatedClass.constructor(PRIVATE);
        JVar overloadedConstructorContextParam = overloadedConstructor.param(getClasses().CONTEXT, STRING_CONTEXT);
        JVar overloadedConstructorRootFragmentParam
            = overloadedConstructor.param(getClasses().OBJECT, STRING_ROOT_FRACTION);
        JBlock overloadedConstructorBody = overloadedConstructor.body();
        if (superConstructor.getParameters().size() == 1) {
            overloadedConstructorBody.invokeSuper().arg(constructorContextParam);
        }
        overloadedConstructorBody.assign(getContextField(), overloadedConstructorContextParam);
        overloadedConstructorBody.assign(getRootFragmentField(), overloadedConstructorRootFragmentParam);
    }

    /**
     * 获得上下文字段
     * getContextField
     *
     * @return contextField
     */
    public JFieldVar getContextField() {
        if (contextField == null) {
            contextField = generatedClass.field(PRIVATE, getClasses().CONTEXT, STRING_CONTEXT + generationSuffix());
        }
        return contextField;
    }

    /**
     * 得到根片段领域
     *
     * @return rootFragmentField
     */
    public JFieldVar getRootFragmentField() {
        if (rootFragmentField == null) {
            rootFragmentField = generatedClass.field(PRIVATE, getClasses().OBJECT,
                STRING_ROOT_FRACTION + generationSuffix());
        }
        return rootFragmentField;
    }

    /**
     * 设置上下文裁判
     */
    @Override
    protected void setContextRef() {
        contextRef = getContextField();
    }

    /**
     * 设置根片段裁判
     */
    @Override
    protected void setRootFragmentRef() {
        rootFractionRef = getRootFragmentField();
    }

    /**
     * 设置初始化
     */
    @Override
    protected void setInit() {
        init = generatedClass.method(PRIVATE, getCodeModel().VOID, "init" + generationSuffix());
    }

    /**
     * 构造函数中调用初始化
     */
    public void invokeInitInConstructors() {
        JBlock constructorBody = constructor.body();
        constructorBody.invoke(getInit());

        JBlock overloadedConstructorBody = overloadedConstructor.body();
        overloadedConstructorBody.invoke(getInit());
    }

    /**
     * 创建工厂方法
     *
     * @param scope 范围
     */
    public void createFactoryMethod(EBean.Scope scope) {
        AbstractJClass narrowedGeneratedClass
            = codeModelHelper.narrowGeneratedClass(generatedClass, annotatedElement.asType());

        JMethod factoryMethod = generatedClass.method(PUBLIC | STATIC | SYNCHRONIZED,
            narrowedGeneratedClass, GET_INSTANCE_METHOD_NAME);
        codeModelHelper.generify(factoryMethod, annotatedElement);

        JVar factoryMethodContextParam = factoryMethod.param(getClasses().CONTEXT, STRING_CONTEXT);

        JBlock factoryMethodBody = factoryMethod.body();

        switch (scope) {
            case Default:
                factoryMethodBody._return(_new(narrowedGeneratedClass).arg(factoryMethodContextParam));
                createOverloadedFactoryMethod(scope);
                break;

            case Fraction:
                JVar beanVar = factoryMethodBody.decl(getGeneratedClass(), STRING_BEAN,
                    _new(narrowedGeneratedClass).arg(factoryMethodContextParam));
                factoryMethodBody.invoke(beanVar, getInit());
                factoryMethodBody._return(beanVar);

                createOverloadedFactoryMethod(scope);
                break;

            case Ability:
                requestInstanceFromBeanHolder(factoryMethodContextParam, factoryMethodContextParam, factoryMethodBody);

                beanVar = factoryMethodBody.decl(getGeneratedClass(), STRING_BEAN,
                    _new(narrowedGeneratedClass).arg(factoryMethodContextParam));
                factoryMethodBody.invoke(beanVar, getInit());
                factoryMethodBody._return(beanVar);
                break;

            case Singleton:
                JFieldVar instanceField = generatedClass.field(PRIVATE | STATIC, generatedClass,
                    "instance" + generationSuffix());

                JBlock creationBlock = factoryMethodBody._if(instanceField.eq(_null()))._then();
                JVar previousNotifier = viewNotifierHelper.replacePreviousNotifierWithNull(creationBlock);
                creationBlock.assign(instanceField, _new(narrowedGeneratedClass)
                    .arg(factoryMethodContextParam.invoke("getApplicationContext")));
                creationBlock.invoke(instanceField, getInit());
                viewNotifierHelper.resetPreviousNotifier(creationBlock, previousNotifier);

                factoryMethodBody._return(instanceField);
                break;
            default:
                break;
        }
    }

    /**
     * 创建的工厂方法
     *
     * @param scope 范围
     */
    private void createOverloadedFactoryMethod(EBean.Scope scope) {
        AbstractJClass narrowedGeneratedClass
            = codeModelHelper.narrowGeneratedClass(generatedClass, annotatedElement.asType());
        JMethod factoryMethod = generatedClass.method(PUBLIC | STATIC,
            narrowedGeneratedClass, GET_INSTANCE_METHOD_NAME);
        codeModelHelper.generify(factoryMethod, annotatedElement);

        JVar factoryMethodContextParam = factoryMethod.param(getClasses().CONTEXT, STRING_CONTEXT);
        JVar factoryMethodRootFragmentParam = factoryMethod.param(getClasses().OBJECT, STRING_ROOT_FRACTION);

        JBlock factoryMethodBody = factoryMethod.body();

        if (scope == EBean.Scope.Fraction) {
            requestInstanceFromBeanHolder(factoryMethodRootFragmentParam, factoryMethodContextParam, factoryMethodBody);
        }

        factoryMethodBody._return(_new(narrowedGeneratedClass)
            .arg(factoryMethodContextParam).arg(factoryMethodRootFragmentParam));
    }

    /**
     * 从豆夹请求实例
     *
     * @param fieldRef 现场裁判
     * @param contextRef 上下文裁判
     * @param block 块
     */
    private void requestInstanceFromBeanHolder(IJExpression fieldRef, IJExpression contextRef, JBlock block) {
        AbstractJClass beanHolderClass = getJClass(BeanHolder.class);

        JBlock ifBlock = block._if(fieldRef._instanceof(beanHolderClass))._then();
        JVar beanHolderVar = ifBlock.decl(beanHolderClass, "beanHolder", cast(beanHolderClass, fieldRef));
        JVar beanVar = ifBlock.decl(getGeneratedClass(), STRING_BEAN,
            beanHolderVar.invoke("getBean").arg(getGeneratedClass().dotclass()));

        JInvocation newBeanExpression = _new(getGeneratedClass()).arg(contextRef);
        if (fieldRef != contextRef) {
            newBeanExpression = newBeanExpression.arg(fieldRef);
        }

        JBlock ifBeanNullBlock = ifBlock._if(beanVar.eq(_null()))._then();
        ifBeanNullBlock.assign(beanVar, newBeanExpression);
        ifBeanNullBlock.add(beanHolderVar.invoke("putBean").arg(getGeneratedClass().dotclass()).arg(beanVar));
        ifBeanNullBlock.invoke(beanVar, getInit());

        ifBlock._return(beanVar);
    }

    /**
     * 创建重新装订方法
     */
    public void createRebindMethod() {
        JMethod rebindMethod = generatedClass.method(PUBLIC, getCodeModel().VOID, "rebind");
        JVar contextParam = rebindMethod.param(getClasses().CONTEXT, STRING_CONTEXT);
        JBlock body = rebindMethod.body();
        body.assign(getContextField(), contextParam);
        body.invoke(getInit());
    }

    /**
     * 得到标签
     *
     * @param tag 标签
     * @return {@link JFieldVar}
     */
    public JFieldVar getTag(String tag) {
        if (tagField == null) {
            createHiLogTag(tag);
        }
        return tagField;
    }

    /**
     * 创建嗨日志标记
     *
     * @param tag 标签
     */
    private void createHiLogTag(String tag) {
        JDefinedClass generatedClass = getGeneratedClass();
        JInvocation labelJInvocation = _new(getClasses().HI_LOG_LABEL).arg(getClasses().HI_LOG.staticRef("LOG_APP"))
            .arg(0x000001).arg(tag);
        tagField = generatedClass.field(PRIVATE | STATIC | FINAL,
            getClasses().HI_LOG_LABEL, "TAG", labelJInvocation);
    }
}
